using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Catastrophe.Common.ObjM
{
	public class FastInvoker
	{
		private delegate object CreateObject();

		private static Dictionary<int, OpCode> s_number2Opcode;

		private static ConcurrentDictionary<Type, CreateObject> _constrcache;

		static FastInvoker()
		{
			FastInvoker.s_number2Opcode = new Dictionary<int, OpCode>();
			FastInvoker._constrcache = new ConcurrentDictionary<Type, CreateObject>();
			FastInvoker.s_number2Opcode.Add(-1, OpCodes.Ldc_I4_M1);
			FastInvoker.s_number2Opcode.Add(0, OpCodes.Ldc_I4_0);
			FastInvoker.s_number2Opcode.Add(1, OpCodes.Ldc_I4_1);
			FastInvoker.s_number2Opcode.Add(2, OpCodes.Ldc_I4_2);
			FastInvoker.s_number2Opcode.Add(3, OpCodes.Ldc_I4_3);
			FastInvoker.s_number2Opcode.Add(4, OpCodes.Ldc_I4_4);
			FastInvoker.s_number2Opcode.Add(5, OpCodes.Ldc_I4_5);
			FastInvoker.s_number2Opcode.Add(6, OpCodes.Ldc_I4_6);
			FastInvoker.s_number2Opcode.Add(7, OpCodes.Ldc_I4_7);
			FastInvoker.s_number2Opcode.Add(8, OpCodes.Ldc_I4_8);
		}

		public static FastInvokeHandler CreateHandler<T>(string funName) where T : class
		{
			MethodInfo methodInfo = FastInvoker.ReadMethodInfo<T>(funName);
			return FastInvoker.CreateHandler(methodInfo);
		}

		public static FastInvokeHandler CreateHandler(MethodInfo methodInfo)
		{
			ParameterInfo[] parameters = methodInfo.GetParameters();
			Type[] paramTypes = FastInvoker.ReadAllParameterType(parameters);
			DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[2]
			{
				typeof(object),
				typeof(object[])
			}, methodInfo.DeclaringType!.Module);
			ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
			LocalBuilder[] locals = FastInvoker.CreateLocalParameters(iLGenerator, paramTypes);
			FastInvoker.SaveParamToLocals(iLGenerator, paramTypes, locals);
			FastInvoker.TryEmitObjectAddress(iLGenerator, methodInfo);
			FastInvoker.EmitLocal(iLGenerator, locals, parameters);
			FastInvoker.EmitCall(iLGenerator, methodInfo);
			FastInvoker.EmitReturn(iLGenerator, methodInfo, paramTypes, parameters, locals);
			return (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
		}

		private static MethodInfo ReadMethodInfo<T>(string funName) where T : class
		{
			Type typeFromHandle = typeof(T);
			return typeFromHandle.GetMethod(funName);
		}

		private static Type[] ReadAllParameterType(ParameterInfo[] parameters)
		{
			Type[] array = new Type[parameters.Length];
			for (int i = 0; i < array.Length; i++)
			{
				if (parameters[i].ParameterType.IsByRef)
				{
					array[i] = parameters[i].ParameterType.GetElementType();
				}
				else
				{
					array[i] = parameters[i].ParameterType;
				}
			}
			return array;
		}

		private static LocalBuilder[] CreateLocalParameters(ILGenerator msil, Type[] paramTypes)
		{
			LocalBuilder[] array = new LocalBuilder[paramTypes.Length];
			for (int i = 0; i < paramTypes.Length; i++)
			{
				array[i] = msil.DeclareLocal(paramTypes[i], pinned: true);
			}
			return array;
		}

		private static void SaveParamToLocals(ILGenerator msil, Type[] paramTypes, LocalBuilder[] locals)
		{
			for (int i = 0; i < paramTypes.Length; i++)
			{
				msil.Emit(OpCodes.Ldarg_1);
				FastInvoker.EmitInt(msil, i);
				msil.Emit(OpCodes.Ldelem_Ref);
				FastInvoker.EmitCastToReference(msil, paramTypes[i]);
				msil.Emit(OpCodes.Stloc, locals[i]);
			}
		}

		private static void EmitCastToReference(ILGenerator msil, Type type)
		{
			if (type.IsValueType)
			{
				msil.Emit(OpCodes.Unbox_Any, type);
			}
			else
			{
				msil.Emit(OpCodes.Castclass, type);
			}
		}

		private static void TryEmitObjectAddress(ILGenerator msil, MethodInfo methodInfo)
		{
			if (!methodInfo.IsStatic)
			{
				msil.Emit(OpCodes.Ldarg_0);
			}
		}

		private static void EmitLocal(ILGenerator msil, LocalBuilder[] locals, ParameterInfo[] parameters)
		{
			for (int i = 0; i < parameters.Length; i++)
			{
				if (parameters[i].ParameterType.IsByRef)
				{
					msil.Emit(OpCodes.Ldloca_S, locals[i]);
				}
				else
				{
					msil.Emit(OpCodes.Ldloc, locals[i]);
				}
			}
		}

		private static void EmitCall(ILGenerator msil, MethodInfo methodInfo)
		{
			if (methodInfo.IsStatic)
			{
				msil.EmitCall(OpCodes.Call, methodInfo, null);
			}
			else
			{
				msil.EmitCall(OpCodes.Callvirt, methodInfo, null);
			}
		}

		private static void EmitReturn(ILGenerator msil, MethodInfo methodInfo, Type[] paramTypes, ParameterInfo[] parameters, LocalBuilder[] locals)
		{
			if (methodInfo.ReturnType == typeof(void))
			{
				msil.Emit(OpCodes.Ldnull);
			}
			else if (methodInfo.ReturnType.IsValueType)
			{
				msil.Emit(OpCodes.Box, methodInfo.ReturnType);
			}
			for (int i = 0; i < paramTypes.Length; i++)
			{
				if (parameters[i].ParameterType.IsByRef)
				{
					msil.Emit(OpCodes.Ldarg_1);
					FastInvoker.EmitInt(msil, i);
					msil.Emit(OpCodes.Ldloc, locals[i]);
					if (locals[i].LocalType.IsValueType)
					{
						msil.Emit(OpCodes.Box, locals[i].LocalType);
					}
					msil.Emit(OpCodes.Stelem_Ref);
				}
			}
			msil.Emit(OpCodes.Ret);
		}

		private static void EmitInt(ILGenerator msil, int value)
		{
			OpCode value2 = OpCodes.Ldc_I4_M1;
			if (FastInvoker.s_number2Opcode.TryGetValue(value, out value2))
			{
				msil.Emit(value2);
			}
			else if (value > -129 && value < 128)
			{
				msil.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
			}
			else
			{
				msil.Emit(OpCodes.Ldc_I4, value);
			}
		}

		public static object FastCreateInstance(Type objtype)
		{
			if ((object)objtype == null)
			{
				throw new ArgumentNullException("objtype");
			}
			try
			{
				CreateObject value = null;
				if (FastInvoker._constrcache.TryGetValue(objtype, out value))
				{
					return value();
				}
				if (objtype.IsClass)
				{
					DynamicMethod dynamicMethod = new DynamicMethod("_fcic", objtype, null, restrictedSkipVisibility: true);
					ILGenerator iLGenerator = dynamicMethod.GetILGenerator();
					iLGenerator.Emit(OpCodes.Newobj, objtype.GetConstructor(Type.EmptyTypes));
					iLGenerator.Emit(OpCodes.Ret);
					value = (CreateObject)dynamicMethod.CreateDelegate(typeof(CreateObject));
					FastInvoker._constrcache.TryAdd(objtype, value);
				}
				else
				{
					DynamicMethod dynamicMethod2 = new DynamicMethod("_fcis", typeof(object), null, restrictedSkipVisibility: true);
					ILGenerator iLGenerator2 = dynamicMethod2.GetILGenerator();
					LocalBuilder local = iLGenerator2.DeclareLocal(objtype);
					iLGenerator2.Emit(OpCodes.Ldloca_S, local);
					iLGenerator2.Emit(OpCodes.Initobj, objtype);
					iLGenerator2.Emit(OpCodes.Ldloc_0);
					iLGenerator2.Emit(OpCodes.Box, objtype);
					iLGenerator2.Emit(OpCodes.Ret);
					value = (CreateObject)dynamicMethod2.CreateDelegate(typeof(CreateObject));
					FastInvoker._constrcache.TryAdd(objtype, value);
				}
				return value();
			}
			catch (Exception innerException)
			{
				throw new Exception($"Failed to fast create instance for type '{objtype.FullName}' from assembly '{objtype.AssemblyQualifiedName}'", innerException);
			}
		}
	}
}
